package fr.n7.sma.model;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.swing.event.EventListenerList;

import fr.n7.sma.model.listeners.AgentListener;
import fr.n7.sma.model.listeners.AgentNewResourcesListener;
import fr.n7.sma.model.listeners.events.AgentEvent;
import fr.n7.sma.model.listeners.events.AgentEvent.EventType;
import fr.n7.sma.util.FiniteSizeLinkedList;

public class Agent<AdditionnalProperty, CellAdditionnalProperty> {
	
	public static int MAX_ACTION_NUMBER = 100;

	private AdditionnalProperty additionnalProperty;
	
	//Properties
	private String name;

	//Environment
	private GridModel<Cell<CellAdditionnalProperty>, AdditionnalProperty, CellAdditionnalProperty> grid;
	private Map<String, Short> nearResources;
	private double cellNeighboursNbInv;

	//Agent Life
	private short[] lastPosition;
	private short[] position;
	private Map<String, Integer> stepNbToLastResources;
	private String heldResourceName;
	private FiniteSizeLinkedList<AgentAction> life;

	//Listeners
	private EventListenerList listeners;

	private AgentAction lastAction;
	private boolean useAttractor;
	private boolean putPhero;

	private int direction;

	public Agent(
			String name,
			GridModel<Cell<CellAdditionnalProperty>, AdditionnalProperty, CellAdditionnalProperty> grid,
			short[] position,
			List<String> recognizableResourceNames,
			int memoryLength,
			boolean useAttractor,
			boolean putPhero) {
		this.name = name;
		this.grid = grid;
		this.position = position;
		this.useAttractor = useAttractor;
		this.putPhero = putPhero;
//		this.deplAngle = deplAngle;
		this.listeners = new EventListenerList();
		direction = (int)(Math.random()*8);
		stepNbToLastResources = new ConcurrentHashMap<String, Integer>();
		for(String s : recognizableResourceNames){
			stepNbToLastResources.put(s, 0);
		}
		this.life = new FiniteSizeLinkedList<AgentAction>(memoryLength);
		cellNeighboursNbInv = 1./(double)grid.getNeighboursNb();
	}

	/**
	 * This method makes the agent try to put or to take a resource.
	 * If it has not succeeded, it walks.
	 */
	public void doNextStep() {
		synchronized (grid) {
			boolean nextStepCarriedOut = false;
			nearResources = capter();
			if(heldResourceName != null){
				// The agent carry a resource, can it put it ?
				nextStepCarriedOut = tryToPut();
			}else{
				// The agent doesn't carry a resource, can it take one ?
				nextStepCarriedOut = tryToTake();
			}
			if(!nextStepCarriedOut){
				walk();
			}
		}
	}

	/**
	 * This method makes the agent try to put a resource.
	 * @return whether it has succeeded or not.
	 */
	private boolean tryToPut(){
		boolean result = false;
		Short rsNb = nearResources.get(heldResourceName);
		if(rsNb != null && rsNb != 0 && grid.getCellAt(position).isFree()){
			//There is at least one resource of same kind around agent
			//and the cell it is on, is free.
			
			//So it try to put a resource.
			double rand = Math.random();
			if(rand < rsNb*cellNeighboursNbInv){
				put(position, heldResourceName);
				result = true;
			}
		}
		return result;
	}

	/**
	 * This method makes the agent try to take a resource.
	 * @return whether it has succeeded or not.
	 */
	private boolean tryToTake(){
		//Variables initialization
		boolean result = false;
		Map<String, Short> hereResources = grid.getCellAt(position).getResources();
		String minName = null;
		Short minSize = null;
		boolean end = false;
		Short rsNb;
		Short nearrsNb;
		String name;
		//Let's take the most rare resource
		Iterator<String> itR = hereResources.keySet().iterator();
		while(!end && itR.hasNext()){
			name = itR.next();
			rsNb = hereResources.get(name);
			if(rsNb != null && rsNb!=0){
				//There is a resource here !
				nearrsNb = nearResources.get(name);
				if(nearrsNb == null || nearrsNb == 0){
					//No resource of same kind around ! It's one of the most rare !
					minSize = 0;
					minName = name;
					end = true;
				}else if(minSize == null || nearrsNb < minSize){
					//This resource is the most rare for the moment !
					minSize = nearrsNb;
					minName = name;
				}
			}
		}
		if(end || (minSize != null && Math.random() >= minSize*cellNeighboursNbInv)){
			take(position, minName);
			result = true;
		}
		return result;
	}

	public void setMemoryLength(int length){
		life.setMaxLength(length);
	}
	
	public void useAttractor(boolean useAttractor){
		this.useAttractor = useAttractor;
	}

	public void putPhero(Boolean putPhero) {
		this.putPhero = putPhero;
	}
	

	private void oneMoreAction(AgentAction a){
		lastAction = a;
		life.add(a);
	}

	private void walk() {
		lastPosition = position;
		direction = grid.getDirection(position, direction, useAttractor);
		position = grid.getNeighbour(position, direction);
		if(putPhero){
			this.grid.putPhero(position,20);
		}
		oneMoreAction(new AgentAction(lastPosition, position));
		notifyAgentListeners(new AgentEvent<AdditionnalProperty, CellAdditionnalProperty>(this, lastPosition, position));
	}

	public void setPosition(short[] p){
		position = p;
		lastPosition = p;
		notifyAgentListeners(new AgentEvent<AdditionnalProperty, CellAdditionnalProperty>(this, lastPosition, position));
	}
	
	private void take(short[] p, String rname) {
		grid.getCellAt(p).removeOneResource(rname);
		heldResourceName = rname;
		oneMoreAction(new AgentAction(AgentAction.Action.TAKE, position, rname));
		notifyAgentListeners(new AgentEvent<AdditionnalProperty, CellAdditionnalProperty>(
				EventType.RESOURCE_TAKEN , this, rname));
	}

	private void put(short[] p, String rname) {
		synchronized (grid) {
			grid.getCellAt(p).addOneResource(rname);
			heldResourceName = null;
			oneMoreAction(new AgentAction(AgentAction.Action.PUT, position, rname));
			notifyAgentListeners(new AgentEvent<AdditionnalProperty, CellAdditionnalProperty>(
					EventType.RESOURCE_PUT , this, rname));
		}
	}

	private Map<String, Short> capter(){
		Map<String, Short> result = new HashMap<String, Short>();
		result = grid.getCellAt(position).getNeighboursResources();
		
		// update the number of step between consecutive resources
		for(String s : stepNbToLastResources.keySet()){
			int n = stepNbToLastResources.get(s);
			if(grid.getCellAt(position).getResources().containsKey(s)){
				//The cell contains s
				if(n!=0){
					notifyResourcesListeners(s, n);
					stepNbToLastResources.put(s, 0);
				}
			}else{
				//The cell doesn't contain s
				stepNbToLastResources.put(s, n+1);
			}
		}
		return result;
	}

	public void addAgentListener(AgentListener<AdditionnalProperty, CellAdditionnalProperty> l){
		listeners.add(AgentListener.class, l);
	}

	public void removeAgentListener(AgentListener<AdditionnalProperty, CellAdditionnalProperty> l){
		listeners.remove(AgentListener.class, l);
	}

	private void notifyAgentListeners(AgentEvent<AdditionnalProperty, CellAdditionnalProperty> event){
		for(AgentListener<AdditionnalProperty, CellAdditionnalProperty> l : listeners.getListeners(AgentListener.class)){
			l.agentChanged(event);
		}
	}

	public void addResourcesListener(AgentNewResourcesListener l){
		listeners.add(AgentNewResourcesListener.class, l);
	}

	public void removeResourcesListener(AgentNewResourcesListener l){
		listeners.remove(AgentNewResourcesListener.class, l);
	}

	private void notifyResourcesListeners(String name, int deplNumberFromLast){
		for(AgentNewResourcesListener l : listeners.getListeners(AgentNewResourcesListener.class)){
			l.newResourceDiscovered(name, deplNumberFromLast);
		}
	}

	/*
	private void walk() {
		double r = Math.random();
		//List<Position> neighbours = grid.getNeighbours(position);
		//int neighbourNb = grid.getNeighboursNb();
		double ra = 360*r;
		synchronized(memory){
			memory.add(position);
		}
		lastPosition = position;
		grid.getCellAt(position).putTrace(new Trace(traceColor));
		position = grid.getNeighbour(position, ra);
		notifyAgentListeners();
	}
	 */

	public Map<String, Integer> getStepNbToLastResources(){
		return stepNbToLastResources;
	}

	public Map<String, Short> getNearResources(){
		return nearResources;
	}

	public String getName() {
		return name;
	}

	public short[] getLastPosition() {
		return lastPosition;
	}

	public short[] getPosition() {
		return position;
	}

	public String getHeldResourceName(){
		return heldResourceName;
	}

	public String toString(){
		return name+" : "+life.toString();
	}

	public AdditionnalProperty getAdditionnalProperty() {
		return additionnalProperty;
	}

	public void setAdditionnalProperty(AdditionnalProperty additionnalProperty) {
		this.additionnalProperty = additionnalProperty;
	}
	
	public String lifeToString(int nb, Map<String, Integer> map){
		StringBuffer sb = new StringBuffer();
		for(AgentAction aa : life){
			sb.append(aa.toString(map)+":");
			if(nb-- == 0) break;
		}
		return sb.toString();
	}

	public FiniteSizeLinkedList<AgentAction> getLife() {
		return life;
	}

	public AgentAction getLastAction() {
		return lastAction;
	}

}
